""" This module controls the overall engine and implements a few key aspects.

    2008-08-22:
    
    Okay, as an ammendment to the below, it turns out that this module only needs
    to be dependent on the video and event modules but not the camera or world
    modules. This makes it more like the old core module than I expected it to
    be.

    2008-08-22:
    
    The old way of thinking was that the entire `sbak` package constituted the
    engine and the engine had a "core" which had to be independent of all other
    modules. This was very limiting in what could be done as far as adding
    built-in features to the engine which would have been beneficial for reasons
    of speed and a producing sensible API. Therefore, this idea is being replaced
    with a new concept. The new way of thinking is that the entire `sbak` module
    is not the engine but is actually just a library whose contents merely
    //includes// the engine.
    
    The exact set of modules which make up the engine isn't really solid, but that
    doesn't really matter. This module, `engine` certainly does not comprise the
    entire engine, but it does implement a few very important parts of it and
    may be considered as a kind of "nexus" for the various engine parts. Note,
    however, that it is not the same as a "core" in that it is not an
    independently-operating component to which all other components connect.
    Instead, it merges functionality of other modules together into a somewhat
    intricate network.
"""

import pygame
import video
import event

import error
from event import Event

from constants import (
    EVT_BEGIN,
    EVT_END,
    EVT_UPDATE,
    EVT_DRAW,
    EVT_COLLISIONCHECK,
    EVT_COLLISION,
    DEFAULT_TICKS
)

__all__ = (
    'init',
    'update',
    'finish',
    'start'
)

_init = False

def init(ticks=DEFAULT_TICKS):
    """ Initializes the engine module.
        
        This must be called before using the other functions of this module.
        
        However, be sure to initialize the other modules on which this module
        depends before initializing this module.
        
        Calling sbak.init() will call this function and initialize all other
        modules in the correct order.
        It is safe to call this multiple times. Multiple calls are ignored.
    """
    global _init, _ticks, _clock, _quit, _hooks
    if not _init:
        _init = True
        _clock = pygame.time.Clock()
        _ticks = ticks or DEFAULT_TICKS
        _quit = False
        _hooks = {}

def quit():
    """ Close down the module.
    
        After this is called, you will have to call init() again before
        you can use anything from the module.
        
        NOT safe to call multiple times.
    """
    global _init, _ticks, _clock, _quit, _hooks
    
    if not _init:
        raise error.Error("Engine quit before initialization!")
    
    set_finish_events(False,False)
    del _ticks, _clock, _quit, _hooks
    _init = False

def update():
    """ Updates engine internals.
        
        
    """
    

def start(quitOnEscape=True, quitOnClose=True):
    """ Starts the main game loop and continues it until finish() is called.
    
        ~~~ ARGUMENTS ~~~
    
        quitOnEscape:   If True (default) pressing "escape" on the
                        keyboard will call engine.quit().
        
        quitOnClose:    If True (default) pressing the "X" on the
                        window corner will call engine.quit().
        
        ~~~ DESCRIPTION ~~~
        
        The main game loop uses the `event` module to post special
        "game-regulating" events. These include:
        
        ~~~ GAME-REGULATING EVENTS ~~~
        
        END_BEGIN - This signifies the beginning of a game cycle.
        ...       - Pygame events, such as KEYUP, KEYDOWN, etc.
        
        EVT_UPDATE- Main game update cycle. This is where most logic
                    is handled.
                    
        EVT_COLLISIONCHECK -
                    Sent to make worlds and entities check for
                    collisions. No actual collision handling occurs
                    here, but COLLISION events may be posted as a
                    result.
                    
        EVT_END   - Signifies the end of a game cycle.
        
        EVT_DRAW  - Signifies that it is time for things to be drawn
                    to the screen.
                    
                    Code in here should be strictly related to
                    displaying objects on the screen as there is a
                    chance that this event will not occur once every
                    cycle, or may even occur multiple times per cycle
                    (i.e. if some kind of frameskip is implemented,
                    etc.)
                    
                    This has a .dest attribute which refers to the
                    surface this draw is occuring on. Currently, this
                    will always be the screen surface.
                    
        There is another event, EVT_COLLISION, which is posted in response
        to EVT_COLLISIONCHECK. At the time of this writing ScrollBack does
        not explicitly post any EVT_COLLISION events itself, but it is
        expected that EVT_COLLISION events will only ever be posted while
        the EVT_COLLISIONCHECK event is being handled.
        
        Note that multiple EVT_COLLISION events may occur per game cycle
        (one per collision that occurs). Because they are posted after
        all the event handlers begin firing, they will not be handled
        until after the EVT_DRAW event of the current cycle (if a EVT_DRAW
        event actually occurs) has completed, and before the EVT_BEGIN
        event of the next cycle. THIS MAY CHANGE IN FUTURE VERSIONS!
    """
    global _quit
    
    set_finish_events(quitOnEscape, quitOnClose)
    
    _quit=False
    while not _quit:
        # Cause the correct events to occur in order
        event.post_ex(EVT_BEGIN)
        event.pump()
        event.post_ex(EVT_UPDATE)
        event.post_ex(EVT_COLLISIONCHECK)
        event.post_ex(EVT_END)
        event.post_ex(EVT_DRAW, dest=video.get_screen())
        # Last few things that must always be last:
        event.update()
        video.update()
        _clock.tick(_ticks)
    
def finish(evt=None):
    """ Causes the main game loop to end.
    
        Accepts a single "evt" parameter so that it can be easily used
        in an event hook. (This is not used, however.)
        
        NOTE: This function does NOT close the module, i.e. it stays
        "itialized" and ready to go even after finish is called. To
        close down the module (so as to make it re-initializable) you
        call `engine.quit()`.
    """
    global _quit
    _quit = True
    set_finish_events(False,False)

def set_finish_events(onEscape=True, onClose=True):
    """ Activate or deactivate finishing on certain events.
    
        onEscape:   If True, the engine will finish when the "escape"
                    key is pressed.
        
        onClose:    If True, the engine will finish when the "close"
                    key is pressed.
                    
        This is used to create or remove event hooks that are
        triggered to call engine.finish().
        
        Note to developers: I know that this existence of this function
        and related features seems sort of frivelous, especially
        considering how easy it would be for the library user to
        implement the finish behavior themselves. However, adding this
        functionality just makes one less thing that new users have to
        worry about, which is what we want.
    """
    
    if 'finish_on_escape' in _hooks and not onEscape:
        _hooks['finish_on_escape'].remove()
        del _hooks['finish_on_escape']
    
    if 'finish_on_escape' not in _hooks and onEscape:
        _hooks['finish_on_escape'] = event.create_hook(finish, 'keydown',
                                                     key='escape')
    
    if 'finish_on_close' in _hooks and not onClose:
        _hooks['finish_on_close'].remove()
        del _hooks['finish_on_close']
    
    if 'finish_on_close' not in _hooks and onClose:
        _hooks['finish_on_close'] = event.create_hook(finish, 'quit')

def get_fps():
    """ Returns the current frames per second. """
    return _clock.get_fps()
